package cap

import (
	"context"
	"encoding/json"
	"sync"
	"time"

	"github.com/redis/go-redis/v9"
)

// CaptchaStorage 定义验证码存储接口
type CaptchaStorage interface {
	// Set 存储验证码结果
	Set(ctx context.Context, id string, result CaptchaResult, expiration time.Duration) error
	// Get 获取验证码结果
	Get(ctx context.Context, id string) (CaptchaResult, error)
	// Delete 删除验证码结果
	Delete(ctx context.Context, id string) error
}

// MemoryStorageItem 包含验证码结果和过期时间
type MemoryStorageItem struct {
	Result     CaptchaResult
	Expiration time.Time
}

// MemoryStorage 实现基于内存的验证码存储，支持过期时间
// 这是默认的存储实现

type MemoryStorage struct {
	storage map[string]MemoryStorageItem
	mutex   sync.RWMutex // 用于保证并发安全
}

// NewMemoryStorage 创建内存存储实例
func NewMemoryStorage() *MemoryStorage {
	ms := &MemoryStorage{
		storage: make(map[string]MemoryStorageItem),
	}
	// 启动一个后台goroutine定期清理过期的验证码
	go ms.cleanupExpiredCaptchas()
	return ms
}

// Set 存储验证码结果到内存
func (ms *MemoryStorage) Set(ctx context.Context, id string, result CaptchaResult, expiration time.Duration) error {
	ms.mutex.Lock()
	defer ms.mutex.Unlock()
	
	// 计算过期时间
	expTime := time.Now().Add(expiration)
	ms.storage[id] = MemoryStorageItem{
		Result:     result,
		Expiration: expTime,
	}
	return nil
}

// Get 从内存获取验证码结果
func (ms *MemoryStorage) Get(ctx context.Context, id string) (CaptchaResult, error) {
	ms.mutex.RLock()
	item, exists := ms.storage[id]
	// 先获取当前时间，避免在获取锁的过程中时间变化导致的不一致
	now := time.Now()
	ms.mutex.RUnlock()
	
	if !exists {
		return CaptchaResult{}, ErrCaptchaNotFound
	}
	
	// 检查是否过期
	if now.After(item.Expiration) {
		// 异步删除过期项
		go ms.Delete(ctx, id)
		return CaptchaResult{}, ErrCaptchaNotFound
	}
	
	return item.Result, nil
}

// Delete 从内存删除验证码结果
func (ms *MemoryStorage) Delete(ctx context.Context, id string) error {
	ms.mutex.Lock()
	defer ms.mutex.Unlock()
	delete(ms.storage, id)
	return nil
}

// cleanupExpiredCaptchas 定期清理过期的验证码
func (ms *MemoryStorage) cleanupExpiredCaptchas() {
	ticker := time.NewTicker(1 * time.Minute) // 每1分钟清理一次，更及时地释放内存
	defer ticker.Stop()
	
	for {
		select {
		case <-ticker.C:
			ms.mutex.Lock()
			now := time.Now()
			// 创建一个临时切片存储需要删除的键，避免在遍历时修改map
			var expiredKeys []string
			for id, item := range ms.storage {
				if now.After(item.Expiration) {
					expiredKeys = append(expiredKeys, id)
				}
			}
			// 批量删除过期的验证码
			for _, id := range expiredKeys {
				delete(ms.storage, id)
			}
			ms.mutex.Unlock()
		}
	}
}

// RedisStorage 实现基于Redis的验证码存储
type RedisStorage struct {
	client *redis.Client
	prefix string // Redis键前缀
}

// NewRedisStorage 创建Redis存储实例
func NewRedisStorage(client *redis.Client) *RedisStorage {
	return NewRedisStorageWithPrefix(client, "captcha:")
}

// NewRedisStorageWithPrefix 创建指定前缀的Redis存储实例
func NewRedisStorageWithPrefix(client *redis.Client, prefix string) *RedisStorage {
	return &RedisStorage{
		client: client,
		prefix: prefix,
	}
}

// Set 存储验证码结果到Redis
func (rs *RedisStorage) Set(ctx context.Context, id string, result CaptchaResult, expiration time.Duration) error {
	// 将CaptchaResult序列化为JSON
	jsonData, err := json.Marshal(result)
	if err != nil {
		return err
	}

	// 存储到Redis并设置过期时间
	return rs.client.Set(ctx, rs.prefix+id, jsonData, expiration).Err()
}

// Get 从Redis获取验证码结果
func (rs *RedisStorage) Get(ctx context.Context, id string) (CaptchaResult, error) {
	// 从Redis获取数据
	jsonData, err := rs.client.Get(ctx, rs.prefix+id).Bytes()
	if err != nil {
		if err == redis.Nil {
			return CaptchaResult{}, ErrCaptchaNotFound
		}
		return CaptchaResult{}, err
	}

	// 反序列化为CaptchaResult
	var result CaptchaResult
	if err := json.Unmarshal(jsonData, &result); err != nil {
		return CaptchaResult{}, err
	}

	return result, nil
}

// Delete 从Redis删除验证码结果
func (rs *RedisStorage) Delete(ctx context.Context, id string) error {
	return rs.client.Del(ctx, rs.prefix+id).Err()
}

// RedisOptions Redis客户端配置选项
type RedisOptions struct {
	Addr     string // Redis服务器地址，格式为"host:port"
	Password string // Redis密码
	DB       int    // Redis数据库索引
}

// NewRedisClient 创建Redis客户端
func NewRedisClient(options RedisOptions) *redis.Client {
	return redis.NewClient(&redis.Options{
		Addr:     options.Addr,
		Password: options.Password,
		DB:       options.DB,
	})
}

// 自定义错误类型
var (
	// ErrCaptchaNotFound 验证码不存在或已过期
	ErrCaptchaNotFound = NewError("验证码不存在或已过期")
)

// Error 自定义错误结构
type Error struct {
	Message string
}

// NewError 创建自定义错误
func NewError(message string) *Error {
	return &Error{
		Message: message,
	}
}

// Error 返回错误信息
func (e *Error) Error() string {
	return e.Message
}